package com.lt.basic.project.mino.config;

import cn.dev33.satoken.stp.StpUtil;
import com.alibaba.fastjson2.JSON;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.lt.basic.project.application.config.local.ProThreadLocal;
import com.lt.basic.project.application.mapper.mino.user.UserMapper;
import com.lt.basic.project.application.mapper.mino.user.UserVisitsMapper;
import com.lt.basic.project.common.aop.NotLogin;
import com.lt.basic.project.common.aop.NotPower;
import com.lt.basic.project.common.core.AjaxResult;
import com.lt.basic.project.common.enums.ErrorEnum;
import com.lt.basic.project.common.exception.LoginException;
import com.lt.basic.project.common.uid.Id;
import com.lt.basic.project.common.util.IpUtils;
import com.lt.basic.project.common.util.StringUtils;
import com.lt.basic.project.pojo.entity.mino.user.User;
import com.lt.basic.project.pojo.entity.mino.user.UserVisits;
import lombok.NonNull;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.validation.constraints.NotNull;
import java.lang.reflect.Method;
import java.util.Date;

/**
 * 小程序拦截器
 * @author LT
 */
@Slf4j
@Component
public class MinoInterceptor implements HandlerInterceptor {

    @Resource
    UserMapper userMapper;
    @Resource
    UserVisitsMapper userVisitsMapper;

    /**
     * 前置处理器
     *
     * @param request  请求
     * @param response 响应
     * @param handler  处理
     * @return boolean
     * @throws Exception 异常
     */
    @Override
    public boolean preHandle(@NonNull HttpServletRequest request,
                             @NonNull HttpServletResponse response,
                             @NonNull Object handler) throws Exception {



        // 判断请求接口
        response.setContentType("application/json;charset=utf-8");
        String reqUri = request.getRequestURI();

        log.info("==前置请求通知==");
        log.info("==== {} {} ====", request.getMethod(), reqUri);
        log.info("token参数:{}", JSON.toJSONString(request.getHeader("front-token")));
        log.info("请求参数:{}", JSON.toJSONString(request.getParameterMap()));
        log.info("===============");

        UserVisits userVisits = new UserVisits();
        userVisits.setId(Id.getId());
        userVisits.setUserId(0L);
        userVisits.setArgs(JSON.toJSONString(request.getParameterMap()));
        userVisits.setType(request.getMethod());
        userVisits.setMethod(request.getMethod());
        userVisits.setUrl(reqUri);
        userVisits.setIp(IpUtils.getIpAddress());
        userVisits.setIsNew(true);
        userVisits.setCreateTime(new Date());

        if (!(handler instanceof HandlerMethod) || !reqUri.startsWith("/mino")) {
            // 添加访问数据
            userVisitsMapper.insert(userVisits);
            return HandlerInterceptor.super.preHandle(request, response, handler);
        }

        // 记录当前平台
        String terminal = request.getHeader("terminal");
        ProThreadLocal.put("terminal", StringUtils.isEmpty(terminal) ? "1" : terminal);

        // 登录权限校验
        try {
            Method method = this.obtainAop(handler);
            this.checkLogin(method, reqUri);
            // 添加访问数据
            UserVisits visits = userVisitsMapper.selectOne(Wrappers.lambdaQuery(UserVisits.class).eq(UserVisits::getUserId, ProThreadLocal.getUserId()).last(" limit 1"));
            userVisits.setIsNew(null == visits);
            userVisits.setUserId(ProThreadLocal.getUserId());
            userVisitsMapper.insert(userVisits);
        } catch (LoginException e) {
            AjaxResult<Object> result = AjaxResult.failed(e.getCode(), e.getMsg());
            response.getWriter().print(JSON.toJSONString(result));
            return false;
        }

        // 验证通过继续操作
        return HandlerInterceptor.super.preHandle(request, response, handler);
    }

    /**
     * 后置处理器
     *
     * @param request  请求
     * @param response 响应
     * @param handler  处理
     * @param ex       异常
     * @throws Exception 异常
     */
    @Override
    public void afterCompletion(@NonNull HttpServletRequest request,
                                @NonNull HttpServletResponse response,
                                @NonNull Object handler, Exception ex) throws Exception {
        ProThreadLocal.remove();
        HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
    }

    /**
     * 提取注解
     * @param handler 处理器
     * @return Method
     * @throws Exception 异常
     */
    private Method obtainAop(@NotNull Object handler) throws Exception {
        String[] objArr = handler.toString().split("#");
        String methodStr = objArr[1].split("\\(")[0];
        String classStr = objArr[0];
        Class<?> clazz = Class.forName(classStr);

        Method[] methods = clazz.getDeclaredMethods();
        for (Method method : methods) {
            if (method.getName().equals(methodStr)) {
                return method;
            }
        }

        return null;
    }


    /**
     * 登录验证
     *
     * @param method 方法类
     * @author fzr
     */
    private void checkLogin(Method method, String reqUri) {
        for (int i=0; i<=0; i++) {
            // 免登校验
            if (StringUtils.isNotNull(method) && method.isAnnotationPresent(NotLogin.class)) {
                break;
            }

            // 令牌校验
            String token = StpUtil.getTokenValue();
            if (StringUtils.isNull(token) || StringUtils.isBlank(token)) {
                Integer errCode = ErrorEnum.TOKEN_EMPTY.getCode();
                String errMsg = ErrorEnum.TOKEN_EMPTY.getMsg();
                throw new LoginException(errCode, errMsg);
            }

            // 登录校验
            Object id = StpUtil.getLoginId();
            if (StringUtils.isNull(id)) {
                Integer errCode = ErrorEnum.TOKEN_INVALID.getCode();
                String errMsg = ErrorEnum.TOKEN_INVALID.getMsg();
                throw new LoginException(errCode, errMsg);
            }

            // 用户信息缓存
            Integer userId = Integer.parseInt(id.toString());
            User user = userMapper.selectOne(new QueryWrapper<User>()
                    .select("id,sn,username,nickname,mobile,is_disable")
                    .eq("id", userId)
                    .eq("is_delete", 0)
                    .last("limit 1"));

            // 删除校验
            if (StringUtils.isNull(user)) {
                Integer errCode = ErrorEnum.TOKEN_INVALID.getCode();
                String errMsg = ErrorEnum.TOKEN_INVALID.getMsg();
                throw new LoginException(errCode, errMsg);
            }

            // 禁用校验
            if (user.getIsDisable().equals(1)) {
                Integer errCode = ErrorEnum.LOGIN_DISABLE_ERROR.getCode();
                String errMsg = ErrorEnum.LOGIN_DISABLE_ERROR.getMsg();
                throw new LoginException(errCode, errMsg);
            }

            // 写入线程
            ProThreadLocal.put("id", user.getId());
            ProThreadLocal.put("userSn", user.getSn());
            ProThreadLocal.put("username", user.getUsername());
            ProThreadLocal.put("nickname", user.getNickname());
            ProThreadLocal.put("mobile", user.getMobile());

            // 权限校验
            if (user.getId().intValue() != 1) {
                this.checkAuth(method, reqUri);
            }
        }
    }

    /**
     * 权限验证
     *
     * @param method 方法类
     * @param uri 请求路由
     * @author fzr
     */
    private void checkAuth(Method method, String uri) {
        for (int i=0; i<=0; i++) {
            // 免权限校验
            if (StringUtils.isNotNull(method) && method.isAnnotationPresent(NotPower.class)) {
                break;
            }

            // 路由转权限
            String prefix = "/mino/";
            String route = uri.replaceFirst(prefix, "");
            String auths = route.replace("/", ":");

            // 校验权限
            StpUtil.checkPermission(auths);
        }
    }
}
